/**
 * \class ReadTemplate
 * \brief Template for writing a new reader in MOAB
 *
 */

#include "ReadTemplate.hpp"
#include "moab/Interface.hpp"
#include "moab/ReadUtilIface.hpp"
#include "moab/Range.hpp"
#include "moab/FileOptions.hpp"

#include <cstdio>
#include <cassert>

namespace moab
{

ReaderIface* ReadTemplate::factory( Interface* iface )
{
    return new ReadTemplate( iface );
}

ReadTemplate::ReadTemplate( Interface* impl ) : mbImpl( impl ), fileName( NULL )
{
    mbImpl->query_interface( readMeshIface );
}

ReadTemplate::~ReadTemplate()
{
    if( readMeshIface )
    {
        mbImpl->release_interface( readMeshIface );
        readMeshIface = 0;
    }
}

ErrorCode ReadTemplate::read_tag_values( const char* /* file_name */,
                                         const char* /* tag_name */,
                                         const FileOptions& /* opts */,
                                         std::vector< int >& /* tag_values_out */,
                                         const SubsetList* /* subset_list */ )
{
    return MB_NOT_IMPLEMENTED;
}

ErrorCode ReadTemplate::load_file( const char* filename,
                                   const EntityHandle* file_set,
                                   const FileOptions& opts,
                                   const ReaderIface::SubsetList* subset_list,
                                   const Tag* /*file_id_tag*/ )
{
    if( subset_list )
    {
        // See src/moab/ReaderIface.hpp, definition of SubsetList struct; this basically specifies
        // an integer tag and tag values for sets to read on this proc, or a part number and total #
        // parts for reading a trivial partition of entities
    }

    // Save filename to member variable so we don't need to pass as an argument
    // to called functions
    fileName = filename;

    // Process options; see src/FileOptions.hpp for API for FileOptions class, and
    // doc/metadata_info.doc for a description of various options used by some of the readers in
    // MOAB
    ErrorCode result = process_options( opts );MB_CHK_SET_ERR( result, fileName << ": problem reading options" );

    // Open file; filePtr is member of ReadTemplate, change to whatever mechanism is used to
    // identify file
    FILE* filePtr = fopen( fileName, "r" );
    if( !filePtr )
    {
        MB_SET_ERR( MB_FILE_DOES_NOT_EXIST, fileName << ": fopen returned error" );
    }

    // Read number of verts, elements, sets
    long num_verts = 0, num_elems = 0, num_sets = 0;

    // read_ents keeps a running set of entities read from this file, including vertices, elements,
    // and sets; these will get added to file_set (if input) at the end of the read
    Range read_ents;

    // start_vertex is passed back so we know how to convert indices from the file into vertex
    // handles; most of the time this is done by adding start_vertex to the (0-based) index; if the
    // index is 1-based, you also need to subtract one; see read_elements for details
    EntityHandle start_vertex;
    result = read_vertices( num_verts, start_vertex, read_ents );
    if( MB_SUCCESS != result )
    {
        fclose( filePtr );
        return result;
    }

    // Create/read elements; this template assumes that all elements are the same type, so can be
    // read in a single call to read_elements, and kept track of with a single start_elem handle. If
    // there are more entity types, might have to keep these start handles in an array/vector.
    // start_elem is only really needed if you're reading sets later, and need to convert some
    // file-based index to an entity handle
    EntityHandle start_elem;
    result = read_elements( num_elems, start_vertex, start_elem, read_ents );
    if( MB_SUCCESS != result )
    {
        fclose( filePtr );
        return result;
    }

    // Read/create entity sets; typically these sets have some tag identifying what they're for, see
    // doc/metadata_info.doc for examples of different kinds of sets and how they're marked
    result = create_sets( num_sets, start_vertex, num_verts, start_elem, num_elems, read_ents );
    if( MB_SUCCESS != result )
    {
        fclose( filePtr );
        return result;
    }

    // Finally, add all read_ents into the file set, if one was input
    if( file_set && *file_set )
    {
        result = mbImpl->add_entities( *file_set, read_ents );
        if( MB_SUCCESS != result )
        {
            fclose( filePtr );
            return result;
        }
    }

    fclose( filePtr );

    return result;
}

ErrorCode ReadTemplate::read_vertices( int num_verts, EntityHandle& start_vertex, Range& read_ents )
{
    // Allocate nodes; these are allocated in one shot, get contiguous handles starting with
    // start_handle, and the reader is passed back double*'s pointing to MOAB's native storage for
    // vertex coordinates for those verts
    std::vector< double* > coord_arrays;
    ErrorCode result = readMeshIface->get_node_coords( 3, num_verts, 1, start_vertex, coord_arrays );MB_CHK_SET_ERR( result, fileName << ": Trouble reading vertices" );

    // Fill in vertex coordinate arrays
    double *x = coord_arrays[0], *y = coord_arrays[1], *z = coord_arrays[2];
    for( long i = 0; i < num_verts; ++i )
    {
        // Read x/y/z; do something with them for now to avoid warning
        if( x || y || z )
        {
        }
    }

    if( num_verts ) read_ents.insert( start_vertex, start_vertex + num_verts - 1 );

    return result;
}

//! Read/create elements
ErrorCode ReadTemplate::read_elements( int num_elems,
                                       EntityHandle start_vertex,
                                       EntityHandle& start_elem,
                                       Range& read_ents )
{
    // Get the entity type being read
    EntityType ent_type = MBHEX;

    // Get the number of vertices per entity
    int verts_per_elem = 8;

    // Create the element sequence; passes back a pointer to the internal storage for connectivity
    // and the starting entity handle
    EntityHandle* conn_array;
    ErrorCode result =
        readMeshIface->get_element_connect( num_elems, verts_per_elem, ent_type, 1, start_elem, conn_array );MB_CHK_SET_ERR( result, fileName << ": Trouble reading elements" );

    // Read connectivity into conn_array directly
    for( long i = 0; i < num_elems; i++ )
    {
        // Read connectivity
    }

    // Convert file-based connectivity indices to vertex handles in-place; be careful, if indices
    // are smaller than handles, need to do from the end of the list so we don't overwrite data
    //
    // Here, we assume indices are smaller than handles, just for demonstration; create an
    // integer-type pointer to connectivity initialized to same start of connectivity array
    int* ind_array = reinterpret_cast< int* >( conn_array );
    // OFFSET is value of first vertex index in file; most files are 1-based, but some might be
    // 0-based
    int OFFSET = 1;
    for( long i = num_elems * verts_per_elem - 1; i >= 0; i-- )
    {
        conn_array[i] = ind_array[i] + start_vertex + OFFSET;

        // This assert assumes last handle in read_ents is highest vertex handle in this file
        assert( conn_array[i] >= start_vertex && conn_array[i] <= *read_ents.rbegin() );
    }

    // Notify MOAB of the new elements
    result = readMeshIface->update_adjacencies( start_elem, num_elems, verts_per_elem, conn_array );
    if( MB_SUCCESS != result ) return result;

    // Add elements to read_ents
    if( num_elems ) read_ents.insert( start_elem, start_elem + num_elems - 1 );

    return MB_SUCCESS;
}

//! Read/create sets
ErrorCode ReadTemplate::create_sets( int num_sets,
                                     EntityHandle /*start_vertex*/,
                                     int /*num_verts*/,
                                     EntityHandle /*start_elem*/,
                                     int /*num_elems*/,
                                     Range& read_ents )
{
    ErrorCode result = MB_SUCCESS;
    EntityHandle this_set;

    for( int i = 0; i < num_sets; i++ )
    {
        // Create set
        result = mbImpl->create_meshset( MESHSET_SET, this_set );MB_CHK_SET_ERR( result, fileName << ": Trouble creating set" );

        Range set_ents;
        // Read/compute what's in this set; REMEMBER TO CONVERT THESE TO MOAB HANDLES

        // Add them to the set
        result = mbImpl->add_entities( this_set, set_ents );MB_CHK_SET_ERR( result, fileName << ": Trouble putting entities in set" );

        // Add the new set to read_ents
        read_ents.insert( this_set );
    }

    return MB_SUCCESS;
}

ErrorCode ReadTemplate::process_options( const FileOptions& opts )
{
    // Mark all options seen, to avoid compile warning on unused variable
    opts.mark_all_seen();
    return MB_SUCCESS;
}

}  // namespace moab
